﻿using Devil;

#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;

namespace GameToolkit
{
    [ExecuteInEditMode, DefaultExecutionOrder(-1000), DisallowMultipleComponent, AddComponentMenu("Game Toolkit/Culling Group API")]
    public sealed class CullingGroupAPI : MonoBehaviour, ITransformSensor, ICullingBounds
#if UNITY_EDITOR
        , ICullingBoundsDebug
#endif
    {
#pragma warning disable CS0649
        [SerializeField]
        Vector3 m_BoundsCenter;

        [SerializeField, Min(0)]
        float m_Radius = 0.1f;

        [SerializeField]
        private LayerMask m_IncludeLayers;
        public int Layer { get; private set; }

        [SerializeField, Tooltip("不可移动")]
        bool m_IsImmovable;

        // 是否动态对象
        ICullingTag[] mCullingTags;
        ICullingAction[] mCullingActions;
        ICullingComponent[] mCullingComponents;
        Rigidbody[] mRigs;
        bool mWasSleeping;
        public BoundingSphere Bounding { get { return new BoundingSphere(transform.localToWorldMatrix.MultiplyPoint(m_BoundsCenter), m_Radius); } }
        public bool IsValid => this != null;
        private bool mAdded;
        private CullingHandle mHandler;
        private TransformSensor.Sensor mTransSensor;
        public CullingHandle Handler { get { return mHandler; } }
        public CullingCamera ActiveCullingCamera { get { return mHandler.camera; } }
        private bool mIsStarted;
        ITransformSensor[] mSensors;
#pragma warning restore

        public int TagCount { get { return mCullingTags == null ? 0 : mCullingTags.Length; } }
        public ICullingTag GetCullingTag(int index)
        {
            return mCullingTags[index];
        }

        private void Setup()
        {
            Layer = gameObject.layer;
            CullingBounds.FindCullingTags(gameObject, (1 << Layer) | m_IncludeLayers, out mCullingTags, out mCullingActions, out mCullingComponents, out mRigs);
            CullingBounds.sSensorBuffer.Clear();
            for (int i = 0; i < mCullingTags.Length; i++)
            {
                var sensor = mCullingTags[i] as ITransformSensor;
                if (sensor != null)
                    CullingBounds.sSensorBuffer.Add(sensor);
            }
            mSensors = CullingBounds.sSensorBuffer.ToArray();
            CullingBounds.sSensorBuffer.Clear();
#if UNITY_EDITOR
            int cullLv = 0;
            int alwaysLv = 0;
            ECullingUpdateMode mode = 0;
            if (mCullingTags.Length > 0)
            {
                for (int i = 0; i < mCullingTags.Length; i++)
                {
                    var tag = mCullingTags[i];
                    var it = tag as ICullingUpdate;
                    if (it == null || it.UpdateMode == 0)
                        continue;
                    if (it.CulledUpdateDistanceLv > cullLv)
                        cullLv = it.CulledUpdateDistanceLv;
                    if (it.AlwaysUpdateDistanceLv > alwaysLv)
                        alwaysLv = it.AlwaysUpdateDistanceLv;
                    if ((it.UpdateMode & ECullingUpdateMode.Update) != 0)
                    {
                        mode |= ECullingUpdateMode.Update;
                    }
                    else if ((it.UpdateMode & ECullingUpdateMode.FixedUpdate) != 0)
                    {
                        mode |= ECullingUpdateMode.FixedUpdate;
                    }
                    if ((it.UpdateMode & ECullingUpdateMode.Background) != 0)
                    {
                        mode |= ECullingUpdateMode.Background;
                    }
                }

            }
            alwaysUpdateDistanceLv = alwaysLv;
            culledUpdateDistanceLv = cullLv;
            background = (mode & ECullingUpdateMode.Background) != 0;
#endif
        }

        bool ShouldBeAdded()
        {
            if (mIsStarted)
            {
#if UNITY_EDITOR
                if (!Application.isPlaying)
                    return true;
                else
#endif
                    return mCullingTags != null && mCullingTags.Length > 0;
            }
            else
            {
                return false;
            }
        }

        public void RebuildThis()
        {
            if (mAdded)
            {
                mAdded = false;
                CullingManager.Instance.RemoveCullData(this);
                Setup();
                if (ShouldBeAdded())
                {
                    mAdded = true;
                    CullingManager.Instance.AddCullData(this);
                }
            }
            else if (mIsStarted)
            {
                Setup();
            }
        }

        private void OnEnable()
        {
#if UNITY_EDITOR
            if (!gameObject.activeInHierarchy || GlobalUtil.IsPrefabStage(gameObject))
                return;
            if (mIsStarted)
                Setup();
#endif
            if (ShouldBeAdded())
            {
                mAdded = true;
                mWasSleeping = false;
                CullingManager.Instance.AddCullData(this);
            }
        }

        private void Start()
        {
            mIsStarted = true;
#if UNITY_EDITOR
            if (GlobalUtil.IsPrefabStage(gameObject))
                return;
#endif
            Setup();
            if (ShouldBeAdded())
            {
                mAdded = true;
                CullingManager.Instance.AddCullData(this);
            }
        }

        private void OnDisable()
        {
            if (mAdded)
            {
                mAdded = false;
                mHandler.Release();
                CullingManager.Instance.RemoveCullData(this);
            }
        }

        private void OnDestroy()
        {
            mHandler = default(CullingHandle);
            mIsStarted = false;
        }

        void ITransformSensor.OnTransformChanged(Transform trans, ETransformSensorFlag flags)
        {
            //#if UNITY_EDITOR
            if (this == null)
                return;
            //#endif
            if (mSensors != null)
            {
                for (int i = 0; i < mSensors.Length; i++)
                {
                    mSensors[i].OnTransformChanged(trans, flags);
                }
            }
            if ((flags & ETransformSensorFlag.Position) != 0)
            {
                mHandler.Rebounding();
            }
        }

        public void Rebounding()
        {
            mHandler.Rebounding();
            //mRadius = m_BoundsSize.magnitude * 0.5f;
        }

        public bool HandleCullingCamera(CullingHandle handler)
        {
            if (mHandler.camera != null && mHandler.camera != handler.camera)
            {
                Debug.LogErrorFormat(this, "\"{0}\" was bind to more than one CullingCamera", name);
                return false;
            }
            if (mHandler.camera == null && mCullingComponents != null)
            {
                for (int i = 0; i < mCullingComponents.Length; i++)
                {
                    mCullingComponents[i].OnCullingEnabled(handler.camera, this);
                }
            }
            mHandler = handler;
            if (!m_IsImmovable)
            {
                try
                {
                    mTransSensor = TransformSensor.CreateTransformSensor(transform, this);
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                }
            }
            return true;
        }

        public void ReleaseCullingCamera(CullingCamera cam)
        {
            if (mHandler.camera == cam)
            {
                if (mTransSensor != null)
                    mTransSensor.Release();
                if (mCullingActions != null)
                {
                    for (int i = 0; i < mCullingActions.Length; i++)
                    {
                        mCullingActions[i].OnCulling(cam, false, 10000);
                    }
                }
                if (mCullingComponents != null)
                {
                    for (int i = 0; i < mCullingComponents.Length; i++)
                    {
                        mCullingComponents[i].OnCullingDisabled();
                    }
                }
                mHandler = default(CullingHandle);
#if UNITY_EDITOR
                visible = false;
                lv = 4;
                cullUpdate = false;
#endif
            }
        }

        public void ValidateCullingState(CullingCamera culler, bool visible, int distanceLv)
        {
            if (culler != mHandler.camera)
                return;
#if UNITY_EDITOR
            cullUpdate = distanceLv >= 0 && (visible && culledUpdateDistanceLv > distanceLv || alwaysUpdateDistanceLv > distanceLv);
            this.visible = visible;
            this.lv = distanceLv;
#endif
            for (int i = 0; i < mCullingActions.Length; i++)
            {
                var act = mCullingActions[i];
                if (((Object)act) == null)
                    Debug.LogErrorFormat(this, "The object of type {0} has been destroyed but you are still trying to access it.", act.GetType().Name);
                else
                    act.OnCulling(culler, visible, distanceLv);
            }
            if (mRigs != null)
            {
                var sleep = distanceLv >= (int)CullingBounds.RIGIDBODY_DISTANCE_LV || (!visible && distanceLv < 1);
                if (!mWasSleeping && sleep)
                {
                    for (int i = 0; i < mRigs.Length; i++)
                    {
                        var rig = mRigs[i];
                        if (rig != null)
                        {
                            rig.Sleep();
                        }
                            //mRigs[i].Sleep();
                    }
                }
                mWasSleeping = sleep;
            }
        }

#if UNITY_EDITOR
        bool visible;
        int lv;
        bool cullUpdate;
        // 更新距离
        int culledUpdateDistanceLv;
        int alwaysUpdateDistanceLv;
        bool background;

        private void OnValidate()
        {
            //var vol = GetComponent<IVolumetric>();
            //if (vol != null)
            //{
            //    var size = vol.localBounds.size;
            //    m_BoundsCenter = vol.localBounds.center;
            //    m_Radius = (size.x > size.y ? (size.x > size.z ? size.x : size.z)
            //        : (size.y > size.z ? size.y : size.z)) * 0.5f;
            //}
            if (mAdded && (!Application.isPlaying || Layer != gameObject.layer))
            {
                OnDisable();
                OnEnable();
            }
            else if (mHandler.camera != null)
            {
                if (m_IsImmovable && mTransSensor != null)
                {
                    mTransSensor.Release();
                }
                else if (!m_IsImmovable && mTransSensor == null)
                {
                    mTransSensor = TransformSensor.CreateTransformSensor(transform, this);
                }
            }
        }

        //private void OnDrawGizmos()
        //{
        //    if(Selection.activeGameObject == gameObject)
        //    {
        //        Gizmos.DrawIcon(transform.localToWorldMatrix.MultiplyPoint(m_BoundsCenter), "Cull Icon.png", true, color);
        //    }
        //}

        public Color color
        {
            get
            {
                //if(mCullingTags == null || mCullingTags.Length == 0)
                //{
                //    return Color.blue;
                //}
                Color c;
                if (mHandler.camera != null && visible)
                {
                    c = CullingCamera.colors[lv];
                }
                else
                {
                    c = /*Application.isPlaying ?*/ Color.gray /*: Color.cyan*/;
                }
                if (Application.isPlaying && (cullUpdate || background))
                {
                    c *= Mathf.PingPong((cullUpdate ? 4f : 1f) * Time.realtimeSinceStartup, 1f) * (cullUpdate ? 1.4f : 1f) + 0.5f;
                }
                return c;
            }
        }

        void ICullingBoundsDebug.DrawGizmos(CullingState state)
        {
            var pos = transform.localToWorldMatrix.MultiplyPoint(m_BoundsCenter);
            Gizmos.matrix = Matrix4x4.identity;
            Gizmos.color = state.color;
            Gizmos.DrawWireSphere(pos, m_Radius);
            //Gizmos.DrawWireCube(m_BoundsCenter, m_BoundsSize);
            if ((state.type & GizmoType.Selected) != 0)
            {
                Gizmos.color = Gizmos.color * 0.5f;
                Gizmos.DrawSphere(pos, m_Radius);
                //Gizmos.DrawCube(m_BoundsCenter, m_BoundsSize);
            }
        }

        private void OnDrawGizmosSelected()
        {
            Gizmos.color = color;
            Gizmos.matrix = Matrix4x4.identity;
            Gizmos.DrawWireSphere(transform.localToWorldMatrix.MultiplyPoint(m_BoundsCenter), m_Radius);
        }

        bool ICullingBoundsDebug.Raycast(Ray ray, out Vector3 point)
        {
            var o = transform.localToWorldMatrix.MultiplyPoint(m_BoundsCenter);
            if (Vector3.SqrMagnitude(o - ray.origin) < m_Radius * m_Radius)
            {
                point = o;
                return false;
            }
            var cast = MathExt.RaycastSphere(ray.origin, ray.direction, o, m_Radius, out point);
            return cast;
        }

        string ICullingBoundsDebug.Title { get { return this == null ? "" : name; } }

        void ICullingBoundsDebug.Select()
        {
            if (this != null)
                UnityEditor.Selection.activeGameObject = gameObject;
        }
#endif
    }
}